/****************************************************************************
 * arch/arm/src/stm32/stm32_sdadc.c
 *
 * SPDX-License-Identifier: BSD-3-Clause
 * SPDX-FileCopyrightText: 2015-2017 Gregory Nutt. All rights reserved.
 * SPDX-FileCopyrightText: 2009, 2011 Gregory Nutt. All rights reserved.
 * SPDX-FileCopyrightText: 2016 Studelec. All rights reserved.
 * SPDX-FileContributor: Gregory Nutt <gnutt@nuttx.org>
 * SPDX-FileContributor: Marc Rechté <mrechte@studelec-sa.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <stdio.h>
#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <debug.h>
#include <unistd.h>

#include <arch/board/board.h>
#include <nuttx/irq.h>
#include <nuttx/arch.h>
#include <nuttx/analog/adc.h>
#include <nuttx/analog/ioctl.h>

#include "arm_internal.h"
#include "chip.h"
#include "stm32.h"
#include "stm32_dma.h"
#include "stm32_pwr.h"
#include "stm32_sdadc.h"

#ifdef CONFIG_STM32_SDADC

/* Some SDADC peripheral must be enabled */

#if defined(CONFIG_STM32_SDADC1) || defined(CONFIG_STM32_SDADC2) || \
    defined(CONFIG_STM32_SDADC3)

/* This implementation is for the STM32F37XX only */

#ifndef CONFIG_STM32_STM32F37XX
#  error "This chip is not yet supported"
#endif

/* TODO: At the moment there is no implementation
 * for timer and external triggers
 */

#if defined(SDADC_HAVE_TIMER)
#  error "There is no proper implementation for TIMER TRIGGERS at the moment"
#endif

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* RCC reset ****************************************************************/

#define STM32_RCC_RSTR   STM32_RCC_APB2RSTR
#define RCC_RSTR_SDADC1RST RCC_APB2RSTR_SDADC1RST
#define RCC_RSTR_SDADC2RST RCC_APB2RSTR_SDADC2RST
#define RCC_RSTR_SDADC3RST RCC_APB2RSTR_SDADC3RST

/* SDADC interrupts *********************************************************/

#define SDADC_ISR_ALLINTS (SDADC_ISR_JEOCF | SDADC_ISR_JOVRF)

/* SDADC Channels/DMA *******************************************************/

#define SDADC_DMA_CONTROL_WORD (DMA_CCR_MSIZE_16BITS | \
                                DMA_CCR_PSIZE_16BITS | \
                                DMA_CCR_MINC | \
                                DMA_CCR_CIRC)

/****************************************************************************
 * Private Types
 ****************************************************************************/

/* This structure describes the state of one SDADC block */

struct stm32_dev_s
{
  const struct adc_callback_s *cb;
  uint8_t irq;          /* Interrupt generated by this SDADC block */
  uint8_t nchannels;    /* Number of channels */
  uint8_t cchannels;    /* Number of configured channels */
  uint8_t intf;         /* SDADC interface number */
  uint8_t current;      /* Current SDADC channel being converted */
  uint8_t refv;         /* Reference voltage selection */
#ifdef SDADC_HAVE_DMA
  uint8_t dmachan;      /* DMA channel needed by this SDADC */
  bool    hasdma;       /* True: This channel supports DMA */
#endif
#ifdef SDADC_HAVE_TIMER
  uint8_t trigger;      /* Timer trigger selection: see SDADCx_JEXTSEL_TIMxx */
#endif
  uint32_t base;        /* Base address of registers unique to this SDADC
                         * block */
#ifdef SDADC_HAVE_TIMER
  uint32_t tbase;       /* Base address of timer used by this SDADC block */
  uint32_t jextsel      /* JEXTSEL value used by this SDADC block */
  uint32_t pclck;       /* The PCLK frequency that drives this timer */
  uint32_t freq;        /* The desired frequency of conversions */
#endif
#ifdef SDADC_HAVE_DMA
  DMA_HANDLE dma;       /* Allocated DMA channel */

  /* DMA transfer buffer */

  int16_t dmabuffer[SDADC_MAX_SAMPLES];
#endif

  /* List of selected SDADC injected channels to sample */

  uint8_t  chanlist[SDADC_MAX_SAMPLES];
};

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/* ADC Register access */

static uint32_t sdadc_getreg(struct stm32_dev_s *priv, int offset);
static void     sdadc_putreg(struct stm32_dev_s *priv, int offset,
                             uint32_t value);
static void     sdadc_modifyreg(struct stm32_dev_s *priv, int offset,
                                uint32_t clrbits, uint32_t setbits);
#ifdef ADC_HAVE_TIMER
static uint16_t tim_getreg(struct stm32_dev_s *priv, int offset);
static void     tim_putreg(struct stm32_dev_s *priv, int offset,
                           uint16_t value);
static void     tim_modifyreg(struct stm32_dev_s *priv, int offset,
                              uint16_t clrbits, uint16_t setbits);
static void     tim_dumpregs(struct stm32_dev_s *priv,
                             const char *msg);
#endif

static void sdadc_rccreset(struct stm32_dev_s *priv, bool reset);

/* ADC Interrupt Handler */

static int  sdadc_interrupt(int irq, void *context, void *arg);

/* ADC Driver Methods */

static int  sdadc_bind(struct adc_dev_s *dev,
                       const struct adc_callback_s *callback);
static void sdadc_reset(struct adc_dev_s *dev);
static int  sdadc_setup(struct adc_dev_s *dev);
static void sdadc_shutdown(struct adc_dev_s *dev);
static void sdadc_rxint(struct adc_dev_s *dev, bool enable);
static int  sdadc_ioctl(struct adc_dev_s *dev, int cmd,
                        unsigned long arg);
static void sdadc_enable(struct stm32_dev_s *priv, bool enable);

static int  sdadc_set_ch(struct adc_dev_s *dev, uint8_t ch);

#ifdef ADC_HAVE_TIMER
static void sdadc_timstart(struct stm32_dev_s *priv, bool enable);
static int  sdadc_timinit(struct stm32_dev_s *priv);
#endif

#ifdef ADC_HAVE_DMA
static void sdadc_dmaconvcallback(DMA_HANDLE handle, uint8_t isr,
                                  void *arg);
#endif

static void sdadc_startconv(struct stm32_dev_s *priv, bool enable);

/****************************************************************************
 * Private Data
 ****************************************************************************/

/* SDADC interface operations */

static const struct adc_ops_s g_sdadcops =
{
  .ao_bind        = sdadc_bind,
  .ao_reset       = sdadc_reset,
  .ao_setup       = sdadc_setup,
  .ao_shutdown    = sdadc_shutdown,
  .ao_rxint       = sdadc_rxint,
  .ao_ioctl       = sdadc_ioctl,
};

/* SDADC1 state */

#ifdef CONFIG_STM32_SDADC1
static struct stm32_dev_s g_sdadcpriv1 =
{
  .irq         = STM32_IRQ_SDADC1,
  .intf        = 1,
  .base        = STM32_SDADC1_BASE,
  .refv        = SDADC1_REFV,
#ifdef SDADC1_HAVE_TIMER
  .trigger     = CONFIG_STM32_SDADC1_TIMTRIG,
  .tbase       = SDADC1_TIMER_BASE,
  .extsel      = SDADC1_EXTSEL_VALUE,
  .pclck       = SDADC1_TIMER_PCLK_FREQUENCY,
  .freq        = CONFIG_STM32_SDADC1_SAMPLE_FREQUENCY,
#endif
#ifdef SDADC1_HAVE_DMA
  .dmachan     = DMACHAN_SDADC1,
  .hasdma      = true,
#endif
};

static struct adc_dev_s g_sdadcdev1 =
{
  .ad_ops      = &g_sdadcops,
  .ad_priv     = &g_sdadcpriv1,
};
#endif

/* SDADC2 state */

#ifdef CONFIG_STM32_SDADC2
static struct stm32_dev_s g_sdadcpriv2 =
{
  .irq         = STM32_IRQ_SDADC2,
  .base        = STM32_SDADC2_BASE,
  .refv        = SDADC2_REFV,
#ifdef SDADC2_HAVE_TIMER
  .trigger     = CONFIG_STM32_SDADC2_TIMTRIG,
  .tbase       = SDADC2_TIMER_BASE,
  .extsel      = SDADC2_EXTSEL_VALUE,
  .pclck       = SDADC2_TIMER_PCLK_FREQUENCY,
  .freq        = CONFIG_STM32_SDADC2_SAMPLE_FREQUENCY,
#endif
#ifdef SDADC2_HAVE_DMA
  .dmachan     = DMACHAN_SDADC2,
  .hasdma      = true,
#endif
};

static struct adc_dev_s g_sdadcdev2 =
{
  .ad_ops      = &g_sdadcops,
  .ad_priv     = &g_sdadcpriv2,
};
#endif

/* SDADC3 state */

#ifdef CONFIG_STM32_SDADC3
static struct stm32_dev_s g_sdadcpriv3 =
{
  .irq         = STM32_IRQ_SDADC3,
  .base        = STM32_SDADC3_BASE,
  .refv        = SDADC3_REFV,
#ifdef SDADC3_HAVE_TIMER
  .trigger     = CONFIG_STM32_SDADC3_TIMTRIG,
  .tbase       = SDADC3_TIMER_BASE,
  .extsel      = SDADC3_EXTSEL_VALUE,
  .pclck       = SDADC3_TIMER_PCLK_FREQUENCY,
  .freq        = CONFIG_STM32_SDADC3_SAMPLE_FREQUENCY,
#endif
#ifdef SDADC3_HAVE_DMA
  .dmachan     = DMACHAN_SDADC3,
  .hasdma      = true,
#endif
};

static struct adc_dev_s g_sdadcdev3 =
{
  .ad_ops      = &g_sdadcops,
  .ad_priv     = &g_sdadcpriv3,
};
#endif

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: sdadc_getreg
 *
 * Description:
 *   Read the value of an SDADC register.
 *
 * Input Parameters:
 *   priv   - A reference to the SDADC block state
 *   offset - The offset to the register to read
 *
 * Returned Value:
 *   The current contents of the specified register
 *
 ****************************************************************************/

static uint32_t sdadc_getreg(struct stm32_dev_s *priv, int offset)
{
  return getreg32(priv->base + offset);
}

/****************************************************************************
 * Name: sdadc_putreg
 *
 * Description:
 *   Write a value to an SDADC register.
 *
 * Input Parameters:
 *   priv   - A reference to the SDADC block state
 *   offset - The offset to the register to write to
 *   value  - The value to write to the register
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void sdadc_putreg(struct stm32_dev_s *priv, int offset,
                         uint32_t value)
{
  putreg32(value, priv->base + offset);
}

/****************************************************************************
 * Name: sdadc_modifyreg
 *
 * Description:
 *   Modify the value of an SDADC register (not atomic).
 *
 * Input Parameters:
 *   priv   - A reference to the SDADC block state
 *   offset  - The offset to the register to modify
 *   clrbits - The bits to clear
 *   setbits - The bits to set
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void sdadc_modifyreg(struct stm32_dev_s *priv, int offset,
                            uint32_t clrbits, uint32_t setbits)
{
  sdadc_putreg(priv, offset,
              (sdadc_getreg(priv, offset) & ~clrbits) | setbits);
}

/****************************************************************************
 * Name: tim_getreg
 *
 * Description:
 *   Read the value of an SDADC timer register.
 *
 * Input Parameters:
 *   priv   - A reference to the SDADC block state
 *   offset - The offset to the register to read
 *
 * Returned Value:
 *   The current contents of the specified register
 *
 ****************************************************************************/

#ifdef SDADC_HAVE_TIMER
static uint16_t tim_getreg(struct stm32_dev_s *priv, int offset)
{
  return getreg16(priv->tbase + offset);
}
#endif

/****************************************************************************
 * Name: tim_putreg
 *
 * Description:
 *   Write a value to an SDADC timer register.
 *
 * Input Parameters:
 *   priv   - A reference to the SDADC block state
 *   offset - The offset to the register to write to
 *   value  - The value to write to the register
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifdef SDADC_HAVE_TIMER
static void tim_putreg(struct stm32_dev_s *priv, int offset,
                       uint16_t value)
{
  putreg16(value, priv->tbase + offset);
}
#endif

/****************************************************************************
 * Name: tim_modifyreg
 *
 * Description:
 *   Modify the value of an SDADC timer register (not atomic).
 *
 * Input Parameters:
 *   priv   - A reference to the SDADC block state
 *   offset  - The offset to the register to modify
 *   clrbits - The bits to clear
 *   setbits - The bits to set
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifdef SDADC_HAVE_TIMER
static void tim_modifyreg(struct stm32_dev_s *priv, int offset,
                          uint16_t clrbits, uint16_t setbits)
{
  tim_putreg(priv, offset, (tim_getreg(priv, offset) & ~clrbits) | setbits);
}
#endif

/****************************************************************************
 * Name: tim_dumpregs
 *
 * Description:
 *   Dump all timer registers.
 *
 * Input Parameters:
 *   priv   - A reference to the SDADC block state
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifdef SDADC_HAVE_TIMER
static void tim_dumpregs(struct stm32_dev_s *priv, const char *msg)
{
  ainfo("%s:\n", msg);

  /* TODO */
}
#endif

/****************************************************************************
 * Name: sdadc_timstart
 *
 * Description:
 *   Start (or stop) the timer counter
 *
 * Input Parameters:
 *   priv   - A reference to the SDADC block state
 *   enable - True: Start conversion
 *
 * Returned Value:
 *
 ****************************************************************************/

#ifdef SDADC_HAVE_TIMER
static void sdadc_timstart(struct stm32_dev_s *priv, bool enable)
{
  ainfo("enable: %d\n", enable ? 1 : 0);

  if (enable)
    {
      /* Start the counter */

      tim_modifyreg(priv, STM32_GTIM_CR1_OFFSET, 0, GTIM_CR1_CEN);
    }
  else
    {
      /* Disable the counter */

      tim_modifyreg(priv, STM32_GTIM_CR1_OFFSET, GTIM_CR1_CEN, 0);
    }
}
#endif

/****************************************************************************
 * Name: sdadc_timinit
 *
 * Description:
 *   Initialize the timer that drivers the SDADC sampling for this channel
 *   using the pre-calculated timer divider definitions.
 *
 * Input Parameters:
 *   priv   - A reference to the SDADC block state
 *
 * Returned Value:
 *   Zero on success; a negated errno value on failure.
 *
 ****************************************************************************/

#ifdef SDADC_HAVE_TIMER
static int sdadc_timinit(struct stm32_dev_s *priv)
{
  /* TODO */

  aerr("ERROR: not implemented");
  return ERROR;
}
#endif

/****************************************************************************
 * Name: sdadc_startconv
 *
 * Description:
 *   Start (or stop) the SDADC conversion process
 *
 * Input Parameters:
 *   priv   - A reference to the SDADC block state
 *   enable - True: Start conversion
 *
 * Returned Value:
 *
 ****************************************************************************/

static void sdadc_startconv(struct stm32_dev_s *priv, bool enable)
{
  ainfo("enable: %d\n", enable ? 1 : 0);

  if (enable)
    {
      /* Start the conversion of injected channels */

      sdadc_modifyreg(priv, STM32_SDADC_CR2_OFFSET, 0, SDADC_CR2_JSWSTART);
    }
  else
    {
      /* Wait for a possible conversion to stop */

      while ((sdadc_getreg(priv,
                           STM32_SDADC_ISR_OFFSET) & SDADC_ISR_JCIP) != 0);
    }
}

/****************************************************************************
 * Name: sdadc_rccreset
 *
 * Description:
 *   (De)Initializes the SDADC block registers to their default
 *   reset values.
 *
 * Input Parameters:
 *   priv   - A reference to the SDADC block state
 *   reset  - true: to put in reset state, false: to revert to normal state
 *
 * Returned Value:
 *
 ****************************************************************************/

static void sdadc_rccreset(struct stm32_dev_s *priv, bool reset)
{
  uint32_t adcbit;

  /* Pick the appropriate bit in the APB2 reset register.
   */

  switch (priv->intf)
    {
#ifdef CONFIG_STM32_SDADC1
      case 1:
        adcbit = RCC_RSTR_SDADC1RST;
        break;
#endif
#ifdef CONFIG_STM32_SDADC2
      case 2:
        adcbit = RCC_RSTR_SDADC2RST;
        break;
#endif
#ifdef CONFIG_STM32_SDADC3
      case 3:
        adcbit = RCC_RSTR_SDADC3RST;
        break;
#endif
      default:
        return;
    }

  /* Set or clear the selected bit in the APB2 reset register.
   * modifyreg32() disables interrupts.  Disabling interrupts is necessary
   * because the APB2RSTR register is used by several different drivers.
   */

  if (reset)
    {
      /* Enable SDADC reset state */

      modifyreg32(STM32_RCC_RSTR, 0, adcbit);
    }
  else
    {
      /* Release SDADC from reset state */

      modifyreg32(STM32_RCC_RSTR, adcbit, 0);
    }
}

/****************************************************************************
 * Name: sdadc_power_down_idle
 *
 * Description:
 *   Enables or disables power down during the idle phase.
 *
 * Input Parameters:
 *   priv     - A reference to the SDADC block state
 *   pdi_high - true:  The SDADC is powered down when waiting for a start
 *                     event
 *              false: The SDADC is powered up when waiting for a start event
 *
 * Returned Value:
 *   None.
 *
 ****************************************************************************/

#if 0
static void sdadc_power_down_idle(struct stm32_dev_s *priv,
                                  bool pdi_high)
{
  uint32_t regval;

  ainfo("PDI: %d\n", pdi_high ? 1 : 0);

  regval = sdadc_getreg(priv, STM32_SDADC_CR2_OFFSET);

  if ((regval & SDADC_CR2_ADON) == 0)
    {
      regval = sdadc_getreg(priv, STM32_SDADC_CR1_OFFSET);
      if (pdi_high)
        {
          regval |= SDADC_CR1_PDI;
        }
      else
        {
          regval &= ~SDADC_CR1_PDI;
        }

      sdadc_putreg(priv, STM32_SDADC_CR1_OFFSET, regval);
    }
}
#endif

/****************************************************************************
 * Name: sdadc_enable
 *
 * Description:
 *   Enables or disables the specified SDADC peripheral.
 *                  Does not start conversion unless the SDADC is
 *                  triggered by timer
 *
 * Input Parameters:
 *   priv   - A reference to the SDADC block state
 *   enable - true:  enable SDADC conversion
 *            false: disable SDADC conversion
 *
 * Returned Value:
 *
 ****************************************************************************/

static void sdadc_enable(struct stm32_dev_s *priv, bool enable)
{
  uint32_t regval;

  ainfo("enable: %d\n", enable ? 1 : 0);

  regval = sdadc_getreg(priv, STM32_SDADC_CR2_OFFSET);

  if (enable)
    {
      /* Enable the SDADC */

      sdadc_putreg(priv, STM32_SDADC_CR2_OFFSET, regval | SDADC_CR2_ADON);

      /* Wait for the SDADC to be stabilized */

      while (sdadc_getreg(priv, STM32_SDADC_ISR_OFFSET) & SDADC_ISR_STABIP);
    }
  else if ((regval & SDADC_CR2_ADON) != 0)
    {
      /* Ongoing conversions will be stopped implicitly */

      /* Disable the SDADC */

      sdadc_putreg(priv, STM32_SDADC_CR2_OFFSET, regval & ~SDADC_CR2_ADON);
    }
}

/****************************************************************************
 * Name: sdadc_dmaconvcallback
 *
 * Description:
 *   Callback for DMA.  Called from the DMA transfer complete interrupt after
 *   all channels have been converted and transferred with DMA.
 *
 * Input Parameters:
 *   handle - handle to DMA
 *   isr -
 *   arg - SDADC device
 *
 * Returned Value:
 *
 ****************************************************************************/

#ifdef SDADC_HAVE_DMA
static void sdadc_dmaconvcallback(DMA_HANDLE handle,
                                  uint8_t isr, void *arg)
{
  struct adc_dev_s   *dev  = (struct adc_dev_s *)arg;
  struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv;
  int i;

  /* Verify that the upper-half driver has bound its callback functions */

  if (priv->cb != NULL)
    {
      DEBUGASSERT(priv->cb->au_receive != NULL);

      for (i = 0; i < priv->nchannels; i++)
        {
          priv->cb->au_receive(dev, priv->chanlist[priv->current],
                               priv->dmabuffer[priv->current]);
          priv->current++;
          if (priv->current >= priv->nchannels)
            {
              /* Restart the conversion sequence from the beginning */

              priv->current = 0;
            }
        }
    }
}
#endif

/****************************************************************************
 * Name: sdadc_bind
 *
 * Description:
 *   Bind the upper-half driver callbacks to the lower-half implementation.
 *   This must be called early in order to receive SDADC event notifications.
 *
 ****************************************************************************/

static int sdadc_bind(struct adc_dev_s *dev,
                      const struct adc_callback_s *callback)
{
  struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv;

  DEBUGASSERT(priv != NULL);
  priv->cb = callback;
  return OK;
}

/****************************************************************************
 * Name: sdadc_reset
 *
 * Description:
 *   Reset the SDADC device.
 *   This is firstly called whenever the SDADC device is registered by
 *   sdadc_register()
 *   Does mostly the SDAC register setting.
 *   Leave the device in power down mode.
 *   Note that SDACx clock is already enable (for all SDADC) by the
 *   rcc_enableapb2()
 *
 * Input Parameters:
 *   dev     - pointer to the sdadc device structure
 *
 * Returned Value:
 *
 ****************************************************************************/

static void sdadc_reset(struct adc_dev_s *dev)
{
  struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv;
  irqstate_t flags;
  uint32_t setbits = 0;

  ainfo("intf: %d\n", priv->intf);

  /* TODO: why critical ? */

  flags = enter_critical_section();

  /* Enable SDADC reset state */

  sdadc_rccreset(priv, true);

  /* Enable power */

  stm32_pwr_enablesdadc(priv->intf);

  /* Release SDADC from reset state */

  sdadc_rccreset(priv, false);

  /* Enable the SDADC (and wait until it stabilizes) */

  sdadc_enable(priv, true);

  /* Put SDADC in in initialization mode */

  sdadc_putreg(priv, STM32_SDADC_CR1_OFFSET, SDADC_CR1_INIT);

  /* Wait for the SDADC to be ready */

  while ((sdadc_getreg(priv,
                       STM32_SDADC_ISR_OFFSET) &
                       SDADC_ISR_INITRDY) == 0);

  /* Load configurations */

  sdadc_putreg(priv, STM32_SDADC_CONF0R_OFFSET, SDADC_CONF0R_DEFAULT);
  sdadc_putreg(priv, STM32_SDADC_CONF1R_OFFSET, SDADC_CONF1R_DEFAULT);
  sdadc_putreg(priv, STM32_SDADC_CONF2R_OFFSET, SDADC_CONF2R_DEFAULT);

  sdadc_putreg(priv, STM32_SDADC_CONFCHR1_OFFSET, SDADC_CONFCHR1_DEFAULT);
  sdadc_putreg(priv, STM32_SDADC_CONFCHR2_OFFSET, SDADC_CONFCHR2_DEFAULT);

  /* Configuration of the injected channels group */

  sdadc_set_ch(dev, 0);

  /* CR1 ********************************************************************/

  /* Enable interrupt / dma flags, is done later by upper half when opening
   * device by calling sdadc_rxint()
   */

  setbits = SDADC_CR1_INIT; /* remains in init mode while configuring */

  /* Reference voltage */

  setbits |= priv->refv;

  /* Set CR1 configuration */

  sdadc_putreg(priv, STM32_SDADC_CR1_OFFSET, setbits);

  /* CR2 ********************************************************************/

  setbits = SDADC_CR2_ADON; /* leave it ON ! */

  /* TODO: JEXTEN / JEXTSEL */

  /* Number of calibrations is for 3 configurations */

  setbits |= (2 << SDADC_CR2_CALIBCNT_SHIFT);

  /* Set CR2 configuration */

  sdadc_putreg(priv, STM32_SDADC_CR2_OFFSET, setbits);

  /* Release INIT mode ******************************************************/

  sdadc_modifyreg(priv, STM32_SDADC_CR1_OFFSET, SDADC_CR1_INIT, 0);

  /* Calibrate the SDADC */

  sdadc_modifyreg(priv, STM32_SDADC_CR2_OFFSET, 0, SDADC_CR2_STARTCALIB);

  /* Wait for the calibration to complete (may take up to 5ms) */

  while ((sdadc_getreg(priv,
                       STM32_SDADC_ISR_OFFSET) & SDADC_ISR_EOCALF) == 0);

  /* Clear this flag */

  sdadc_modifyreg(priv,
                  STM32_SDADC_CLRISR_OFFSET, SDADC_CLRISR_CLREOCALF, 0);

#ifdef SDADC_HAVE_TIMER
  if (priv->tbase != 0)
    {
      ret = sdadc_timinit(priv);
      if (ret < 0)
        {
          aerr("ERROR: sdadc_timinit failed: %d\n", ret);
        }
    }
#endif

  /* Put the device in low power mode until it is actually used by
   * application code.
   */

  sdadc_enable(priv, false);

  leave_critical_section(flags);

  ainfo("CR1:  0x%08x CR2:  0x%08x\n",
        sdadc_getreg(priv, STM32_SDADC_CR1_OFFSET),
        sdadc_getreg(priv, STM32_SDADC_CR2_OFFSET));

  ainfo("CONF0R: 0x%08x CONF1R: 0x%08x CONF3R: 0x%08x\n",
        sdadc_getreg(priv, STM32_SDADC_CONF0R_OFFSET),
        sdadc_getreg(priv, STM32_SDADC_CONF1R_OFFSET),
        sdadc_getreg(priv, STM32_SDADC_CONF2R_OFFSET));

  ainfo("CONFCHR1: 0x%08x CONFCHR2: 0x%08x JCHGR: 0x%08x\n",
        sdadc_getreg(priv, STM32_SDADC_CONFCHR1_OFFSET),
        sdadc_getreg(priv, STM32_SDADC_CONFCHR2_OFFSET),
        sdadc_getreg(priv, STM32_SDADC_JCHGR_OFFSET));
}

/****************************************************************************
 * Name: sdadc_setup
 *
 * Description:
 *   Configure the ADC. This method is called the first time that the SDADC
 *   device is opened.
 *   This is called by the upper half driver sdadc_open().
 *   This will occur when the port is first
 *   opened in the application code (/dev/sdadcN).
 *   It would be called again after closing all references to this file and
 *   reopening it.
 *   This function wakes up the device and setup the DMA / IRQ
 *
 * Input Parameters:
 *   dev     - pointer to the sdadc device structure
 *
 * Returned Value:
 *
 ****************************************************************************/

static int sdadc_setup(struct adc_dev_s *dev)
{
  struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv;
  int ret;

  /* Wakes up the device */

  sdadc_enable(priv, true);

  /* Setup DMA or interrupt control. Note that either DMA or interrupt is
   * setup not both.
   */

#ifdef SDADC_HAVE_DMA
  if (priv->hasdma)
    {
      /* Setup DMA */

      /* Stop and free DMA if it was started before */

      if (priv->dma != NULL)
        {
          stm32_dmastop(priv->dma);
          stm32_dmafree(priv->dma);
        }

      priv->dma = stm32_dmachannel(priv->dmachan);

      stm32_dmasetup(priv->dma,
                     priv->base + STM32_SDADC_JDATAR_OFFSET,
                     (uint32_t)priv->dmabuffer,
                     priv->nchannels,
                     SDADC_DMA_CONTROL_WORD);

      stm32_dmastart(priv->dma, sdadc_dmaconvcallback, dev, false);
    }
  else
    {
      /* Attach the SDADC interrupt */

      ret = irq_attach(priv->irq, sdadc_interrupt, dev);
      if (ret < 0)
        {
          ainfo("irq_attach failed: %d\n", ret);
          return ret;
        }
    }
#else
  /* Attach the SDADC interrupt */

  ret = irq_attach(priv->irq, sdadc_interrupt, dev);
  if (ret < 0)
    {
      ainfo("irq_attach failed: %d\n", ret);
      return ret;
    }
#endif

  return OK;
}

/****************************************************************************
 * Name: sdadc_shutdown
 *
 * Description:
 *   Disable the ADC.  This method is called when the last instance
 *   of the SDADC device is closed by the user application.
 *   This method reverses the operation the setup method.
 *
 * Input Parameters:
 *   dev     - pointer to the sdadc device structure
 *
 * Returned Value:
 *
 ****************************************************************************/

static void sdadc_shutdown(struct adc_dev_s *dev)
{
  struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv;

  /* Put the device in low power mode */

  sdadc_enable(priv, false);

  /* Disable interrupt / dma  */

  sdadc_rxint(dev, false);

#ifdef SDADC_HAVE_DMA
  if (priv->hasdma)
    {
      /* Stop and free DMA if it was started before */

      if (priv->dma != NULL)
        {
          stm32_dmastop(priv->dma);
          stm32_dmafree(priv->dma);
        }
    }
  else
    {
      /* Disable ADC interrupts and detach the SDADC interrupt handler */

      up_disable_irq(priv->irq);
      irq_detach(priv->irq);
    }
#else
      /* Disable ADC interrupts and detach the SDADC interrupt handler */

      up_disable_irq(priv->irq);
      irq_detach(priv->irq);
#endif
}

/****************************************************************************
 * Name: sdadc_rxint
 *
 * Description:
 *   Call to enable or disable RX interrupts.
 *
 * Input Parameters:
 *
 * Returned Value:
 *
 ****************************************************************************/

static void sdadc_rxint(struct adc_dev_s *dev, bool enable)
{
  struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv;
  uint32_t setbits;

  ainfo("intf: %d enable: %d\n", priv->intf, enable ? 1 : 0);

  /* DMA mode */

#ifdef SDADC_HAVE_DMA
  if (priv->hasdma)
    {
      setbits = SDADC_CR1_JDMAEN; /* DMA enabled for injected channels group */
    }
  else
    {
      /* Interrupt enable for injected channel group overrun
       *  and end of conversion
       */

      setbits = SDADC_CR1_JOVRIE | SDADC_CR1_JEOCIE;
    }
#else
  setbits = SDADC_CR1_JOVRIE | SDADC_CR1_JEOCIE;
#endif

  if (enable)
    {
      /* Enable */

      sdadc_modifyreg(priv, STM32_SDADC_CR1_OFFSET, 0, setbits);
    }
  else
    {
      /* Disable all ADC interrupts and DMA */

      sdadc_modifyreg(priv, STM32_SDADC_CR1_OFFSET,
                      SDADC_CR1_JOVRIE | SDADC_CR1_JEOCIE | SDADC_CR1_JDMAEN,
                      0);
    }
}

/****************************************************************************
 * Name: sdadc_set_ch
 *
 * Description:
 *   Sets the SDADC injected channel group.
 *
 * Input Parameters:
 *   dev - pointer to device structure used by the driver
 *   ch  - ADC channel number + 1. 0 reserved for all configured channels
 *
 * Returned Value:
 *   int - errno
 *
 ****************************************************************************/

static int sdadc_set_ch(struct adc_dev_s *dev, uint8_t ch)
{
  struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv;
  uint32_t bits = 0;
  int i;

  if (ch == 0)
    {
      priv->current   = 0;
      priv->nchannels = priv->cchannels;
    }
  else
    {
      for (i = 0; i < priv->cchannels && priv->chanlist[i] != ch - 1; i++);

      if (i >= priv->cchannels)
        {
          return -ENODEV;
        }

      priv->current   = i;
      priv->nchannels = 1;
    }

  for (i = 0; i < priv->nchannels; i++)
    {
      bits |= (uint32_t)(1 << priv->chanlist[i]);
    }

  sdadc_putreg(priv, STM32_SDADC_JCHGR_OFFSET, bits);

  return OK;
}

/****************************************************************************
 * Name: sdadc_ioctl
 *
 * Description:
 *   All ioctl calls will be routed through this method.
 *
 * Input Parameters:
 *   dev - pointer to device structure used by the driver
 *   cmd - command
 *   arg - arguments passed with command
 *
 * Returned Value:
 *
 ****************************************************************************/

static int sdadc_ioctl(struct adc_dev_s *dev, int cmd, unsigned long arg)
{
  struct stm32_dev_s *priv = (struct stm32_dev_s *)dev->ad_priv;
  int ret = OK;

  switch (cmd)
    {
      case ANIOC_TRIGGER:
        {
          sdadc_startconv(priv, true);
        }
        break;

      case ANIOC_GET_NCHANNELS:
        {
          /* Return the number of configured channels */

          ret = priv->cchannels;
        }
        break;

      default:
        {
          aerr("ERROR: Unknown cmd: %d\n", cmd);
          ret = -ENOTTY;
        }
        break;
    }

  return ret;
}

/****************************************************************************
 * Name: sdadc_interrupt
 *
 * Description:
 *   Common SDADC interrupt handler.
 *
 * Input Parameters:
 *
 * Returned Value:
 *
 ****************************************************************************/

static int sdadc_interrupt(int irq, void *context, void *arg)
{
  struct adc_dev_s *dev = (struct adc_dev_s *)arg;
  struct stm32_dev_s *priv;
  uint32_t regval;
  uint32_t pending;
  int32_t  data;
  uint8_t  chan;

  DEBUGASSERT(dev != NULL && dev->ad_priv != NULL);
  priv = (struct stm32_dev_s *)dev->ad_priv;

  regval  = sdadc_getreg(priv, STM32_SDADC_ISR_OFFSET);
  pending = regval & SDADC_ISR_ALLINTS;
  if (pending == 0)
    {
      return OK;
    }

  /* JOVRF: overrun flag */

  if ((regval & SDADC_ISR_JOVRF) != 0)
    {
      awarn("WARNING: Overrun has occurred!\n");
    }

  /* JEOCF: End of conversion */

  if ((regval & SDADC_ISR_JEOCF) != 0)
    {
      /* Read the converted value and clear JEOCF bit
       * (It is cleared by reading the SDADC_JDATAR)
       */

      data = sdadc_getreg(priv,
                          STM32_SDADC_JDATAR_OFFSET) &
                           SDADC_JDATAR_JDATA_MASK;
      chan = sdadc_getreg(priv,
                          STM32_SDADC_JDATAR_OFFSET) &
                          SDADC_JDATAR_JDATACH_MASK;

      DEBUGASSERT(priv->chanlist[priv->current] == chan);

      /* Verify that the upper-half driver has bound its callback functions */

      if (priv->cb != NULL)
        {
          /* Give the SDADC data to the ADC driver.  The ADC receive() method
           * accepts 3 parameters:
           *
           * 1) The first is the ADC device instance for this SDADC block.
           * 2) The second is the channel number for the data, and
           * 3) The third is the converted data for the channel.
           */

          DEBUGASSERT(priv->cb->au_receive != NULL);
          priv->cb->au_receive(dev, chan, data);
        }

      /* Set the channel number of the next channel that will complete
       * conversion.
       */

      priv->current++;

      if (priv->current >= priv->nchannels)
        {
          /* Restart the conversion sequence from the beginning */

          priv->current = 0;
        }

      /* do no clear this interrupt (cleared by reading data) */

      pending &= ~SDADC_ISR_JEOCF;
    }

  /* Clears interrupt flags, if any */

  if (pending)
    {
      sdadc_putreg(priv, STM32_SDADC_CLRISR_OFFSET, pending);
    }

  return OK;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: stm32_sdadcinitialize
 *
 * Description:
 *   Initialize one SDADC block
 *
 *   The logic is, save and initialize the channel list in the private driver
 *   structure and return the corresponding adc device structure.
 *
 *   Each SDADC will convert the channels indicated each
 *   time a conversion is triggered either by software, timer or external
 *   event. Channels are numbered from 0 - 8 and must be given in order
 *   (contrarily to what says ST RM0313 doc !!!).
 *
 * Input Parameters:
 *   intf      - Could be {1,2,3} for SDADC1, SDADC2, or SDADC3
 *   chanlist  - The list of channels eg. { 0, 3, 7, 8 }
 *   cchannels - Number of channels
 *
 * Returned Value:
 *   Valid ADC device structure reference on success; a NULL on failure
 *
 ****************************************************************************/

struct adc_dev_s *stm32_sdadcinitialize(int intf,
                                        const uint8_t *chanlist,
                                        int cchannels)
{
  struct adc_dev_s   *dev;
  struct stm32_dev_s *priv;
  int i;

  ainfo("intf: %d cchannels: %d\n", intf, cchannels);

  switch (intf)
    {
#ifdef CONFIG_STM32_SDADC1
      case 1:
        ainfo("SDADC1 selected\n");
        dev = &g_sdadcdev1;
        break;
#endif
#ifdef CONFIG_STM32_SDADC2
      case 2:
        ainfo("SDADC2 selected\n");
        dev = &g_sdadcdev2;
        break;
#endif
#ifdef CONFIG_STM32_SDADC3
      case 3:
        ainfo("SDADC3 selected\n");
        dev = &g_sdadcdev3;
        break;
#endif
      default:
        aerr("ERROR: No SDADC interface defined\n");
        return NULL;
    }

  /* Check channel list in order */

  DEBUGASSERT((cchannels <= SDADC_MAX_SAMPLES) && (cchannels > 0));
  for (i = 0; i < cchannels - 1; i++)
    {
      if (chanlist[i] >= chanlist[i + 1])
        {
          aerr("ERROR: SDADC channel list must be given in order\n");
          return NULL;
        }
    }

  /* Configure the selected SDADC */

  priv = (struct stm32_dev_s *)dev->ad_priv;

  priv->cb        = NULL;
  priv->cchannels = cchannels;

  memcpy(priv->chanlist, chanlist, cchannels);

  return dev;
}

#endif /* CONFIG_STM32_SDADC1 || CONFIG_STM32_SDADC2 ||
        * CONFIG_STM32_SDADC3  */
#endif /* CONFIG_STM32_SDADC */
